package io.terminus.lego.shinda.web.security.oauth

import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties
import org.springframework.boot.context.properties.PropertyMapper
import org.springframework.boot.convert.ApplicationConversionService
import org.springframework.core.convert.ConversionException
import org.springframework.security.oauth2.client.registration.ClientRegistration
import org.springframework.security.oauth2.core.AuthorizationGrantType
import org.springframework.security.oauth2.core.ClientAuthenticationMethod

/**
 * 用来加载CustomOauth2ClientProvider，OAuth2ClientProperties，生成第三方Oauth2Client注册对象的
 * @author wangmeng
 * @date 2018/4/21
 */
object CustomOAuth2ClientPropertiesRegistrationAdapter {

    fun getClientRegistrations(properties: OAuth2ClientProperties): Map<String, ClientRegistration> {
        val clientRegistrations = properties.registration.mapValues { getClientRegistration(it.key, it.value, properties.provider) }
        return clientRegistrations
    }

    private fun getClientRegistration(registrationId: String,
                                      properties: OAuth2ClientProperties.Registration,
                                      providers: Map<String, OAuth2ClientProperties.Provider>): ClientRegistration {
        val builder = getBuilder(registrationId, properties.provider, providers)
        val map = PropertyMapper.get().alwaysApplyingWhenNonNull()

        map.from({ properties.clientId })
                .to({ builder.clientId(it) })
        map.from({ properties.clientSecret })
                .to({ builder.clientSecret(it) })
        map.from({ properties.clientAuthenticationMethod }).`as`({ ClientAuthenticationMethod(it) })
                .to({ builder.clientAuthenticationMethod(it) })
        map.from({ properties.authorizationGrantType }).`as`({ AuthorizationGrantType(it) })
                .to({ builder.authorizationGrantType(it) })
        map.from({ properties.redirectUriTemplate }).
                to({ builder.redirectUriTemplate(it) })
        map.from({ properties.scope }).`as` { it.toTypedArray() }
                .to({ builder.scope(*it) })
        map.from<String>({ properties.clientName })
                .to({ builder.clientName(it) })
        return builder.build()
    }

    private fun getBuilder(registrationId: String, configuredProviderId: String?,
                           providers: Map<String, OAuth2ClientProperties.Provider>): ClientRegistration.Builder {
        val providerId = configuredProviderId ?: registrationId
        val provider = getCommonProvider(providerId)
        if (provider == null && !providers.containsKey(providerId)) {
            throw IllegalStateException(
                    getErrorMessage(configuredProviderId, registrationId))
        }
        val builder = if (provider != null)
            provider.getBuilder(registrationId)
        else
            ClientRegistration.withRegistrationId(registrationId)
        if (providers.containsKey(providerId)) {
            return getBuilder(builder, providers[providerId]!!)
        }
        return builder
    }

    private fun getErrorMessage(configuredProviderId: String?,
                                registrationId: String): String {
        return if (configuredProviderId == null)
            "Provider ID must be specified for client registration $registrationId "
        else
            "Unknown provider ID '$configuredProviderId'"
    }

    private fun getBuilder(builder: ClientRegistration.Builder, provider: OAuth2ClientProperties.Provider): ClientRegistration.Builder {
        val map = PropertyMapper.get().alwaysApplyingWhenNonNull()
        map.from({ provider.authorizationUri }).to({ builder.authorizationUri(it) })
        map.from({ provider.tokenUri }).to({ builder.tokenUri(it) })
        map.from({ provider.userInfoUri }).to({ builder.userInfoUri(it) })
        map.from({ provider.jwkSetUri }).to({ builder.jwkSetUri(it) })
        map.from({ provider.userNameAttribute }).to({ builder.userNameAttributeName(it) })
        return builder
    }

    private fun getCommonProvider(providerId: String): CustomOAuth2Provider? {
        try {
            return ApplicationConversionService.getSharedInstance().convert(providerId, CustomOAuth2Provider::class.java)
        } catch (ex: ConversionException) {
            return null
        }

    }


}